package com.z1.core.config;

import java.io.File;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtil {
	
	/**
	 * 根据指定一组包名获得该包以及子包下的所有类
	 * @param parentClass 父类/接口
	 * @param packageNames 包名数组
	 * @param excludeClasses 排除类集
	 * @return
	 * @throws Exception
	 */
	public static <T> List<Class<T>> getAllClassByParentClassInCurrentClassPath(Class<T> parentClass, String[] packageNames) {
		return getAllClassByParentClassInCurrentClassPath(parentClass, packageNames, null);
	}
	public static <T> List<Class<T>> getAllClassByParentClassInCurrentClassPath(Class<T> parentClass, String[] packageNames, List<Class<T>> excludeClasses) {
		if (null == excludeClasses) {
			excludeClasses = new ArrayList<Class<T>>();
		}
		List<Class<T>> classes = new ArrayList<Class<T>>();
		for (String packageName : packageNames) {
			List<Class<T>> curClasses = getAllClassByParentClassInCurrentClassPath(parentClass, null == packageName ? "" : packageName, null, excludeClasses);
			classes.addAll(curClasses);
			excludeClasses.addAll(curClasses);
		}
		return classes;
	}
	
	/**
	 * /**
	 * 根据指定包名获得该包以及子包下的所有类
	 * @param parentClass 父类/接口
	 * @param packageNames 包名数组
	 * @param classes 子类集
	 * @param excludeClasses 排除类集
	 * @return
	 * @throws Exception
	 */
	public static <T> List<Class<T>> getAllClassByParentClassInCurrentClassPath(Class<T> parentClass, String packageName) {
		return getAllClassByParentClassInCurrentClassPath(parentClass, packageName, null, null);
	}
	public static <T> List<Class<T>> getAllClassByParentClassInCurrentClassPath(Class<T> parentClass, String packageName, List<Class<T>> classes) {
		return getAllClassByParentClassInCurrentClassPath(parentClass, packageName, classes, null);
	}
	public static <T> List<Class<T>> getAllClassByParentClassInCurrentClassPath(Class<T> parentClass, String packageName, List<Class<T>> classes, List<Class<T>> excludeClasses) {
		if (null == classes) {
			classes = new ArrayList<Class<T>>();
		}
		if (null == packageName) {
			packageName = "";
		}
		try {
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			String path = packageName.replace(".", "/");
			Enumeration<URL> resources = classLoader.getResources(path);
			while(resources.hasMoreElements()){
				URL resource = resources.nextElement();
				if (null != resource) {
					String protocol = resource.getProtocol();
//					String pkgPath = resource.getPath();
					//代码文件
					if (protocol.equals("file")) {
						findClassFormFile(
							classLoader, 
							new File(resource.getFile()), 
							parentClass, 
							packageName, 
							classes, 
							excludeClasses
						);
					}
					//jar文件
					else if (protocol.equals("jar")) {
						findClassFormJar(
							classLoader, 
							resource, 
							parentClass, 
							packageName, 
							classes, 
							excludeClasses
						);
					}
				}
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		catch (Throwable e) {
			e.printStackTrace();
		}
		return classes;
	}
	
	/**
	 * 根据指定一组Jar包文件获得该包以及子包下的所有类
	 * @param jarFiles Jar包文件数组
	 * @param parentClass 父类/接口
	 * @param packageName 包名数组
	 * @param classes 子类集
	 * @param excludeClasses 排除类集
	 * @return
	 * @throws Exception
	 */
	public static <T> List<Class<T>> getAllClassByParentClassInJarClassPath(File[] jarFiles, Class<T> parentClass, String packageName) throws Exception {
		return getAllClassByParentClassInJarClassPath(jarFiles, parentClass, packageName, null);
	}
	public static <T> List<Class<T>> getAllClassByParentClassInJarClassPath(File[] jarFiles, Class<T> parentClass, String packageName, List<Class<T>> excludeClasses) throws Exception {
		if (null == excludeClasses) {
			excludeClasses = new ArrayList<Class<T>>();
		}
		List<Class<T>> classes = new ArrayList<Class<T>>();
		for (File jarFile : jarFiles) {
			if (jarFile.getName().endsWith(".jar")) {
				List<Class<T>> curClasses = getAllClassByParentClassInJarClassPath(jarFile, parentClass, null == packageName ? "" : packageName, null, excludeClasses);
				classes.addAll(curClasses);
				excludeClasses.addAll(curClasses);
			}
		}
		return classes;
	}
	
	/**
	 * 根据指定Jar包文件获得该包以及子包下的所有类
	 * @param jarFile Jar包文件
	 * @param parentClass 父类/接口
	 * @param packageName 包名数组
	 * @param classes 子类集
	 * @param excludeClasses 排除类集
	 * @return
	 * @throws Exception
	 */
	public static <T> List<Class<T>> getAllClassByParentClassInJarClassPath(File jarFile, Class<T> parentClass, String packageName) throws Exception {
		return getAllClassByParentClassInJarClassPath(jarFile, parentClass, packageName, null, null);
	}
	public static <T> List<Class<T>> getAllClassByParentClassInJarClassPath(File jarFile, Class<T> parentClass, String packageName, List<Class<T>> classes) throws Exception {
		return getAllClassByParentClassInJarClassPath(jarFile, parentClass, packageName, classes, null);
	}
	public static <T> List<Class<T>> getAllClassByParentClassInJarClassPath(File jarFile, Class<T> parentClass, String packageName, List<Class<T>> classes, List<Class<T>> excludeClasses) throws Exception {
		if (null == classes) {
			classes = new ArrayList<Class<T>>();
		}
		if (null == packageName) {
			packageName = "";
		}
		URL url = new URL("jar:" + jarFile.toURI().toString() + "!/");
		findClassFormJar(new URLClassLoader(new URL[]{ url }), url, parentClass, packageName, classes, excludeClasses);
		return classes;
	}
	
	private static <T> void findClassFormFile(ClassLoader classLoader, File directory, Class<T> parentClass, String packageName, List<Class<T>> classes, List<Class<T>> excludeClasses) throws Exception {
		if (null == directory || !directory.exists()) {
			return;
		}
		File[] files = directory.listFiles();
		if (null == files) {
			return;
		}
		for (File file : files) {
			if (file.isDirectory()) {
				findClassFormFile(
					classLoader, 
					file, 
					parentClass, 
					packageName.equals("") ? file.getName() : packageName + "." + file.getName(), 
					classes, 
					excludeClasses
				);
			}
			else if (file.getName().endsWith(".class")) {
				String clazzName = file.getName().substring(0, file.getName().length() - 6);
				if (!packageName.equals("")) {
					clazzName = packageName + "." + clazzName; 
				}
				getClass(classLoader, clazzName, parentClass, classes, excludeClasses);
			}
		}
	}
	
	private static <T> void findClassFormJar(ClassLoader classLoader, URL url, Class<T> parentClass, String packageName, List<Class<T>> classes, List<Class<T>> excludeClasses) throws Exception {
		if (null == url) {
			return;
		}
		JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
		JarFile jarFile = jarURLConnection.getJarFile();
		Enumeration<JarEntry> jarEntries = jarFile.entries();  
        while (jarEntries.hasMoreElements()) {
        	JarEntry jarEntry = jarEntries.nextElement();
        	//类似：sun/security/internal/interfaces/TlsMasterSecret.class
            String jarEntryName = jarEntry.getName();   
            String clazzName = jarEntryName.replace("/", ".");  
            int endIndex = clazzName.lastIndexOf(".");  
            String prefix = null;  
            if (endIndex > 0) {
                String prefix_name = clazzName.substring(0, endIndex);  
                endIndex = prefix_name.lastIndexOf(".");  
                if(endIndex > 0){  
                    prefix = prefix_name.substring(0, endIndex);  
                }  
            }  
            if (prefix != null && jarEntryName.endsWith(".class")) {
            	if (prefix.equals(packageName)) {
            		getClass(classLoader, clazzName.substring(0, clazzName.length() - 6), parentClass, classes, excludeClasses);
            	}
            	else if (prefix.startsWith(packageName)) {
            		getClass(classLoader, clazzName.substring(0, clazzName.length() - 6), parentClass, classes, excludeClasses);
            	}
            }
        }
    }
	
	@SuppressWarnings("unchecked")
	private static <T> void getClass(ClassLoader classLoader, String clazzName, Class<T> parentClass, List<Class<T>> classes, List<Class<T>> excludeClasses) {
		try {
//			Class<?> clazz = Class.forName(clazzName);
			Class<?> clazz = classLoader.loadClass(clazzName);
			if (parentClass.isAssignableFrom(clazz) && !parentClass.equals(clazz)) {
				if (!classes.contains(clazz) && (null == excludeClasses || !excludeClasses.contains(clazz))) {
					classes.add((Class<T>)clazz);
				}
			}
		}
		catch (ClassNotFoundException e) {}
		catch (NoClassDefFoundError e) { }
		catch (Exception e) {}
		catch (Throwable e) {}
	}
	
}
